/* CsJni - .NET / Java Bridge
 * 
 * Copyright 2010 Robert E. Burke Licensed under the Apache License, Version 
 * 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 
 * law or agreed to in writing, software distributed under the License is 
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 * KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics.CodeAnalysis;

namespace CsJni
{
    public sealed class JniObjectArray : JniArray
    {
        // TODO - methods for multi-dimensional arrays - see http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jni.html

        // TODO - store rank and type, so that can unpack multidimensional arrays, return correct type for 
        // GetObjectArrayElement
        private int mArrayRank;
        private JniType mElementType;

        internal JniObjectArray(JniEnvironment environment, IntPtr handle, JniObjectLifecycle lifecycle, int arrayRank, JniType elementType)
            : base(environment, handle, lifecycle)
        {
            mArrayRank = arrayRank;
            mElementType = elementType;
        }

        public override JniConcreteObjectType ConcreteType
        {
            get { return JniConcreteObjectType.ObjectArray; }
        }

        internal int ArrayRank
        {
            get { return mArrayRank; }
        }

        internal JniType ElementType
        {
            get { return mElementType; }
        }

        // OK to suppress, match JNI name & signature
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public static JniObjectArray NewObjectArray(
            JniEnvironment environment,
            int length,
            JniClass jniClass,
            JniObject init
            )
        {
            VerifyEnvironment(environment);
            VerifyInputJniObject(jniClass);
            IntPtr safeInit = (init == null) ? IntPtr.Zero : init.Handle; // OK if initial value is null
            IntPtr result = JniEnvironment.Functions.NewObjectArray(environment.Handle, length, jniClass.Handle, safeInit);
            if (IntPtr.Zero == result)
            {
                JniThrowable.ThrowCurrentException(environment); // null result means an exception occurred
            }
            // EXCEPTION CHECK: Do nothing, already checked for exception above.
            return new JniObjectArray(environment, result, JniObjectLifecycle.Local, -1, JniType.Null);
        }

        public JniObject GetObjectArrayElement(
            JniEnvironment environment,
            int index
            )
        {
            VerifyEnvironment(environment);
            IntPtr result = JniEnvironment.Functions.GetObjectArrayElement(environment.Handle, this.Handle, index);
            JniThrowable.CheckException(environment);
            if (result == IntPtr.Zero)
            {
                return null;
            }
            FaultRankAndElementType();
            if (mArrayRank > 1)
            {
                return NDimensionalArrayFromHandle(environment, result, mArrayRank - 1, mElementType);
            }
            else if (mArrayRank == 1)
            {   // must be an object
                switch (mElementType)
                {
                    case JniType.NonPrimitive: return JniObject.CreateObjectOfType(environment, result, JniConcreteObjectType.Object);
                    case JniType.String: return JniObject.CreateObjectOfType(environment, result, JniConcreteObjectType.String);
                    case JniType.Class: return JniObject.CreateObjectOfType(environment, result, JniConcreteObjectType.Class);
                    case JniType.Throwable: return JniObject.CreateObjectOfType(environment, result, JniConcreteObjectType.Throwable);
                    default: throw new JniException("TODO");
                }
            }
            throw new JniException("TODO"); // should never reach here
        }

        internal static JniObject NDimensionalArrayFromHandle(JniEnvironment environment, IntPtr handle, int arrayRank, JniType elementType)
        {
            if (arrayRank == 1)
            {
                return OneDimensionalArrayFromHandle(environment, handle, elementType);
            }
            else if (arrayRank > 1)
            {
                return new JniObjectArray(environment, handle, JniObjectLifecycle.Local, arrayRank, elementType);
            }
            else
            {
                throw new JniException("TODO");
            }
        }

        private static JniObject OneDimensionalArrayFromHandle(JniEnvironment environment, IntPtr handle, JniType elementType)
        {
            switch (elementType)
            {
                case JniType.NonPrimitive: goto case JniType.Throwable;
                case JniType.String: goto case JniType.Throwable;
                case JniType.Class: goto case JniType.Throwable;
                case JniType.Throwable: return new JniObjectArray(environment, handle, JniObjectLifecycle.Local, 1, elementType);
            }
            JniConcreteObjectType primitiveArrayType = JniConcreteObjectType.None;
            switch (elementType)
            {
                case JniType.Boolean: primitiveArrayType = JniConcreteObjectType.BooleanArray; break;
                case JniType.Byte: primitiveArrayType = JniConcreteObjectType.ByteArray; break;
                case JniType.Char: primitiveArrayType = JniConcreteObjectType.CharArray; break;
                case JniType.Double: primitiveArrayType = JniConcreteObjectType.DoubleArray; break;
                case JniType.Float: primitiveArrayType = JniConcreteObjectType.FloatArray; break;
                case JniType.Int: primitiveArrayType = JniConcreteObjectType.IntArray; break;
                case JniType.Long: primitiveArrayType = JniConcreteObjectType.LongArray; break;
                case JniType.Short: primitiveArrayType = JniConcreteObjectType.ShortArray; break;
                default: throw new JniException("TODO");
            }
            return JniObject.CreateObjectOfType(environment, handle, primitiveArrayType);
        }

        public void SetObjectArrayElement(
            JniEnvironment environment,
            int index,
            JniObject value
            )
        {
            VerifyEnvironment(environment);
            // NOTE: OK if "JniObject value" is null
            IntPtr safeValue = ((value == null) ? IntPtr.Zero : value.Handle);
            JniEnvironment.Functions.SetObjectArrayElement(environment.Handle, this.Handle, index, safeValue);
            JniThrowable.CheckException(environment);
        }

        private void FaultRankAndElementType()
        {
            if (mArrayRank < 0 || mElementType == JniType.Null)
            {
                throw new NotImplementedException();
                // TODO - get class name, extract rank and type
            }
        }
    }
}
